package com.example.blog.service.impl;


import com.example.blog.entity.Article;
import com.example.blog.entity.Category;
import com.example.blog.entity.Tag;
import com.example.blog.entity.User;
import com.example.blog.exception.ResourceNotFoundException;
import com.example.blog.modules.article.dto.ArticleDTO;
import com.example.blog.modules.article.dto.CategoryDTO;
import com.example.blog.modules.article.dto.TagDTO;
import com.example.blog.modules.article.service.ArticleService;
import com.example.blog.modules.user.dto.UserDTO;
import com.example.blog.repository.ArticleRepository;
import com.example.blog.repository.CategoryRepository;
import com.example.blog.repository.TagRepository;
import com.example.blog.repository.UserRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private ArticleRepository articleRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private CategoryRepository categoryRepository;

    @Autowired
    private TagRepository tagRepository;

    @Override
    @Transactional
    public ArticleDTO createArticle(ArticleDTO articleDTO) {
        Article article = new Article();
        updateArticleFromDTO(article, articleDTO);
        
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = authentication.getName();
        User author = userRepository.findByUsername(username)
                .orElseThrow(() -> new ResourceNotFoundException("User", "username", username));
        article.setAuthor(author);
        
        return convertToDTO(articleRepository.save(article));
    }

    @Override
    @Transactional
    public ArticleDTO updateArticle(Long id, ArticleDTO articleDTO) {
        Article article = articleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Article", "id", id));
        updateArticleFromDTO(article, articleDTO);
        return convertToDTO(articleRepository.save(article));
    }

    @Override
    @Transactional
    public void deleteArticle(Long id) {
        Article article = articleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Article", "id", id));
        articleRepository.delete(article);
    }

    @Override
    public ArticleDTO getArticleById(Long id) {
        Article article = articleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Article", "id", id));
        return convertToDTO(article);
    }

    @Override
    public Page<ArticleDTO> getAllPublishedArticles(Pageable pageable) {
        return articleRepository.findByPublishedTrue(pageable)
                .map(this::convertToDTO);
    }

    @Override
    public Page<ArticleDTO> getArticlesByAuthor(Long authorId, Pageable pageable) {
        User author = userRepository.findById(authorId)
                .orElseThrow(() -> new ResourceNotFoundException("User", "id", authorId));
        return articleRepository.findByAuthor(author, pageable)
                .map(this::convertToDTO);
    }

    @Override
    public Page<ArticleDTO> searchArticles(String keyword, Pageable pageable) {
        return articleRepository.searchArticles(keyword, pageable)
                .map(this::convertToDTO);
    }

    @Override
    @Transactional
    public ArticleDTO publishArticle(Long id) {
        Article article = articleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Article", "id", id));
        article.setPublished(true);
        article.setPublishedAt(LocalDateTime.now());
        return convertToDTO(articleRepository.save(article));
    }

    @Override
    @Transactional
    public ArticleDTO unpublishArticle(Long id) {
        Article article = articleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Article", "id", id));
        article.setPublished(false);
        article.setPublishedAt(null);
        return convertToDTO(articleRepository.save(article));
    }

    @Override
    @Transactional
    public void incrementViewCount(Long id) {
        Article article = articleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Article", "id", id));
        article.setViewCount(article.getViewCount() + 1);
        articleRepository.save(article);
    }

    @Override
    public List<ArticleDTO> getPopularArticles() {
        return articleRepository.findTop5ByPublishedTrueOrderByViewCountDesc()
                .stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    @Override
    public List<ArticleDTO> getRelatedArticles(Long articleId) {
        return articleRepository.findRelatedArticles(articleId, PageRequest.of(0, 5))
                .stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    @Override
    public Page<ArticleDTO> getArticlesByCategory(Long categoryId, Pageable pageable) {
        Category category = categoryRepository.findById(categoryId)
                .orElseThrow(() -> new ResourceNotFoundException("Category", "id", categoryId));
        return articleRepository.findByCategoriesContainingAndPublishedTrue(category, pageable)
                .map(this::convertToDTO);
    }

    @Override
    public Page<ArticleDTO> getArticlesByTag(Long tagId, Pageable pageable) {
        Tag tag = tagRepository.findById(tagId)
                .orElseThrow(() -> new ResourceNotFoundException("Tag", "id", tagId));
        return articleRepository.findByTagsContainingAndPublishedTrue(tag, pageable)
                .map(this::convertToDTO);
    }

    private void updateArticleFromDTO(Article article, ArticleDTO dto) {
        article.setTitle(dto.getTitle());
        article.setSummary(dto.getSummary());
        article.setContent(dto.getContent());
        article.setCoverImage(dto.getCoverImage());
        
        if (dto.getCategories() != null) {
            Set<Category> categories = dto.getCategories().stream()
                    .map(categoryDTO -> categoryRepository.findById(categoryDTO.getId())
                            .orElseThrow(() -> new ResourceNotFoundException("Category", "id", categoryDTO.getId())))
                    .collect(Collectors.toSet());
            article.setCategories(categories);
        }
        
        if (dto.getTags() != null) {
            Set<Tag> tags = dto.getTags().stream()
                    .map(tagDTO -> tagRepository.findById(tagDTO.getId())
                            .orElseThrow(() -> new ResourceNotFoundException("Tag", "id", tagDTO.getId())))
                    .collect(Collectors.toSet());
            article.setTags(tags);
        }
    }

    private ArticleDTO convertToDTO(Article article) {
        ArticleDTO dto = new ArticleDTO();
        dto.setId(article.getId());
        dto.setTitle(article.getTitle());
        dto.setSummary(article.getSummary());
        dto.setContent(article.getContent());
        dto.setCoverImage(article.getCoverImage());
        dto.setViewCount(article.getViewCount());
        dto.setPublished(article.isPublished());
        dto.setCreatedAt(article.getCreatedAt());
        dto.setUpdatedAt(article.getUpdatedAt());
        dto.setPublishedAt(article.getPublishedAt());

        UserDTO authorDTO = new UserDTO();
        authorDTO.setId(article.getAuthor().getId());
        authorDTO.setUsername(article.getAuthor().getUsername());
        authorDTO.setAvatar(article.getAuthor().getAvatar());
        dto.setAuthor(authorDTO);

        Set<CategoryDTO> categoryDTOs = article.getCategories().stream()
                .map(category -> {
                    CategoryDTO categoryDTO = new CategoryDTO();
                    categoryDTO.setId(category.getId());
                    categoryDTO.setName(category.getName());
                    categoryDTO.setDescription(category.getDescription());
                    return categoryDTO;
                })
                .collect(Collectors.toSet());
        dto.setCategories(categoryDTOs);

        Set<TagDTO> tagDTOs = article.getTags().stream()
                .map(tag -> {
                    TagDTO tagDTO = new TagDTO();
                    tagDTO.setId(tag.getId());
                    tagDTO.setName(tag.getName());
                    return tagDTO;
                })
                .collect(Collectors.toSet());
        dto.setTags(tagDTOs);

        return dto;
    }
} 